package com.lin.netty.improve.粘包与半包;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.extern.slf4j.Slf4j;

/**
 * 1粘包问题；客户端发送多条报文，结果合在一起了
 * * 现象，发送 abc def，接收 abcdef
 * * 原因
 *   * 应用层：接收方 ByteBuf 设置太大（Netty 默认 1024）
 *   * 滑动窗口：假设发送方 256 bytes 表示一个完整报文，但由于接收方处理不及时且窗口大小足够大，这 256 bytes 字节就会缓冲在接收方的滑动窗口中，当滑动窗口中缓冲了多个报文就会粘包
 *   * Nagle 算法：会造成粘包
 *
 * 2半包问题：单条消息拆分为多条
 * * 现象，发送 abcdef，接收 abc def
 * * 原因
 *   * 应用层：接收方 ByteBuf 小于实际发送数据量
 *   * 滑动窗口：假设接收方的窗口只剩了 128 bytes，发送方的报文大小是 256 bytes，这时放不下了，只能先发送前 128 bytes，等待 ack 后才能发送剩余部分，这就造成了半包
 *   * MSS 限制：当发送的数据超过 MSS 限制后，会将数据切分发送，就会造成半包
 *
 * 本质是因为 TCP 是流式协议，消息无边界
 *
 * 解决方案(界定消息的边界)：
 * 1. 短链接，发一个包建立一次连接，这样连接建立到连接断开之间就是消息的边界，缺点效率太低
 * 2. 每一条消息采用固定长度，缺点浪费空间
 * 3. 每一条消息采用分隔符，例如 \n，缺点需要转义
 * 4. 每一条消息分为 head 和 body，head 中包含 body 的长度
 * LengthFieldBasedFrameDecoder netty中提供的一个消息入站处理器，用于分隔包含消息的长度头部和消息实体内容的消息
 *   lengthFieldOffset   = 1 (= the length of HDR1)         长度偏移量，从一个字节后开始读取长度
 *   lengthFieldLength   = 2                                长度的字节数，2个字节，这里的结果是0x000C=12，表示消息内容有12个字节
 *   lengthAdjustment    = 1 (= the length of HDR2)         长度调整，就是要跳过1个字节的HDR2开始读取12个字节消息内容
 *   initialBytesToStrip = 3 (= the length of HDR1 + LEN)   最后要剔除的字节数，3个字节，剔除掉HDR1 + LEN
 *
 *   BEFORE DECODE (16 bytes)                       AFTER DECODE (13 bytes)
 *   +------+--------+------+----------------+      +------+----------------+
 *   | HDR1 | Length | HDR2 | Actual Content |----->| HDR2 | Actual Content |
 *   | 0xCA | 0x000C | 0xFE | "HELLO, WORLD" |      | 0xFE | "HELLO, WORLD" |
 *   +------+--------+------+----------------+      +------+----------------+
 */
@Slf4j
public class Client {
    public static void main(String[] args) {
        NioEventLoopGroup workers = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap()
                    .group(workers)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<NioSocketChannel>() {
                        @Override
                        protected void initChannel(NioSocketChannel ch) throws Exception {
                            ch.pipeline().addLast("o1", new ChannelInboundHandlerAdapter() {
                                @Override
                                public void channelActive(ChannelHandlerContext ctx) throws Exception { // 连接建立成功后，会触发Active事件
                                    // 想给服务器发10次消息
                                    for (int i = 0; i < 10; i++) {
                                        ctx.writeAndFlush("1234567890");
                                    }
                                }
                            });
                        }
                    });

            ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 1122).sync();
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            log.error("client error", e);
        } finally {
            workers.shutdownGracefully();
        }

    }
}
